/*
  @title: metroidvania
  @author: Ajay S
  @tags: ['puzzle', 'adventure']
  @description: A metroidvania-style adventure game where you explore interconnected rooms using various tools. Wield a fire wand for combat, use a harmonica to solve musical puzzles, and equip speed boots for enhanced movement. Features boss battles, secret codes, and puzzle-solving mechanics. Use W,A,S,D to move, J for fire wand, I for harmonica, L for speed boots, and K to reset the current room.
  @addedOn: 2024-01-15

  W,A,S,D to move.
  K to reset current room.

  Use the button of the tool you already wield to unequip the tool.
  J to use fireWand
  I to use harmonica
  L to use speedBoots

  The harmonica needs two inputs for one note to be played.
*/

const fluteTest = "1357";
const fluteBoxes = "4321";
const fluteIceTest = "6352";
const fluteExit = "3456";
const fluteTeleport = "1111";

const mainTune = tune`
566.0377358490566: C4~566.0377358490566,
566.0377358490566: E4~566.0377358490566 + E5^566.0377358490566,
566.0377358490566: G4~566.0377358490566,
566.0377358490566: C4~566.0377358490566,
566.0377358490566: E4~566.0377358490566 + C5^566.0377358490566,
566.0377358490566: G4~566.0377358490566,
566.0377358490566: D4~566.0377358490566,
566.0377358490566: F4~566.0377358490566,
566.0377358490566: A4~566.0377358490566,
566.0377358490566: D5^566.0377358490566 + D4~566.0377358490566,
566.0377358490566: F4~566.0377358490566,
566.0377358490566: D4~566.0377358490566,
566.0377358490566: G4^566.0377358490566 + E4~566.0377358490566,
566.0377358490566: G4~566.0377358490566,
566.0377358490566: B4~566.0377358490566,
566.0377358490566: D4~566.0377358490566,
566.0377358490566: G4~566.0377358490566,
566.0377358490566: B4~566.0377358490566,
566.0377358490566: D4~566.0377358490566,
566.0377358490566: F4~566.0377358490566,
566.0377358490566: A4~566.0377358490566,
566.0377358490566: D4~566.0377358490566 + C5^566.0377358490566,
566.0377358490566: F4~566.0377358490566,
566.0377358490566: C4~566.0377358490566 + G4^566.0377358490566,
566.0377358490566: E4~566.0377358490566,
566.0377358490566: G4~566.0377358490566,
566.0377358490566: C4~566.0377358490566,
566.0377358490566: E4~566.0377358490566,
566.0377358490566: G4~566.0377358490566 + E5^566.0377358490566,
566.0377358490566: C4~566.0377358490566,
566.0377358490566: E4~566.0377358490566 + G5^566.0377358490566,
566.0377358490566: G4~566.0377358490566`;
const startTune = tune`
500: C4~500 + A4/500,
500: D4~500,
500: F4~500,
500: D4~500 + C5/500,
500: F4~500 + E5-500,
500: A4~500 + D4^500 + F5-500,
500: C5/500 + F4^500,
500: A4^500 + D4~500 + D5-500,
500: C4~500,
500: E4~500 + B4/500,
500: G4~500,
500: C4~500,
500: E4~500 + B4/500,
500: G4~500 + C4^500,
500: E4^500,
500: G4/500 + C4~500,
500: D4~500,
500: F4~500,
500: A4~500 + C5/500,
500: D4~500 + E5-500,
500: F4~500 + F5-500,
500: A4~500 + C5-500 + D4^500,
500: F4^500 + D5-500,
500: A4^500,
500: C4~500,
500: E4~500 + B4/500,
500: G4~500,
500: C4~500,
500: E4~500 + B4/500,
500: G4~500 + C4^500,
500: E4^500,
500: G4^500 + C5/500`;
const icyTune = tune`
588.2352941176471: C4~588.2352941176471,
588.2352941176471: G4^588.2352941176471 + E4~588.2352941176471,
588.2352941176471: G4^588.2352941176471 + E4~588.2352941176471,
588.2352941176471: C4~588.2352941176471,
588.2352941176471: G4^588.2352941176471 + E4~588.2352941176471,
588.2352941176471: E4~588.2352941176471,
588.2352941176471: D4~588.2352941176471,
588.2352941176471: F4~588.2352941176471 + B4^588.2352941176471,
588.2352941176471: A4^588.2352941176471 + F4~588.2352941176471,
588.2352941176471: D4~588.2352941176471,
588.2352941176471: C5^588.2352941176471 + F4~588.2352941176471,
588.2352941176471: B4^588.2352941176471 + F4~588.2352941176471,
588.2352941176471: E4~588.2352941176471,
588.2352941176471: G4~588.2352941176471 + C5^588.2352941176471,
588.2352941176471: G4~588.2352941176471 + B4^588.2352941176471,
588.2352941176471: E4~588.2352941176471,
588.2352941176471: G4~588.2352941176471 + D5^588.2352941176471,
588.2352941176471: G4~588.2352941176471 + B4^588.2352941176471,
588.2352941176471: D4~588.2352941176471,
588.2352941176471: F4~588.2352941176471 + B4^588.2352941176471,
588.2352941176471: F4~588.2352941176471 + A4^588.2352941176471,
588.2352941176471: D4~588.2352941176471,
588.2352941176471: F4~588.2352941176471 + A4^588.2352941176471,
588.2352941176471: C4~588.2352941176471 + A4^588.2352941176471,
588.2352941176471: E4~588.2352941176471 + G4^588.2352941176471,
588.2352941176471: E4~588.2352941176471 + G4^588.2352941176471,
588.2352941176471: C4~588.2352941176471,
588.2352941176471: E4~588.2352941176471 + G4^588.2352941176471,
588.2352941176471: E4~588.2352941176471,
588.2352941176471: C4~588.2352941176471,
588.2352941176471: E4~588.2352941176471 + G4^588.2352941176471,
588.2352941176471: E4~588.2352941176471`;
const bossTune = tune`
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + A4~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + A4~157.06806282722513,
157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513 + C5~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513 + A4~157.06806282722513 + E5~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
314.13612565445027,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513,
157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + E5~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513 + C5~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + A4~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
314.13612565445027,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513,
157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + A4~157.06806282722513 + C5~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513 + E5~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
314.13612565445027,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513,
157.06806282722513: C4~157.06806282722513 + D4~157.06806282722513,
157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + E5~157.06806282722513,
157.06806282722513: D4~157.06806282722513 + C4~157.06806282722513 + C5~157.06806282722513`;

const duckTune = tune`
410.958904109589: C4~410.958904109589 + F5^410.958904109589,
410.958904109589: F4~410.958904109589,
410.958904109589: G4~410.958904109589,
410.958904109589: A4~410.958904109589 + C5~410.958904109589 + G5^410.958904109589,
410.958904109589,
410.958904109589: C4~410.958904109589 + E5^410.958904109589,
410.958904109589: F4~410.958904109589,
410.958904109589: G4~410.958904109589,
410.958904109589: A4~410.958904109589 + C5~410.958904109589 + G5^410.958904109589,
410.958904109589,
410.958904109589: C4~410.958904109589 + E5^410.958904109589,
410.958904109589: F4~410.958904109589,
410.958904109589: G4~410.958904109589,
410.958904109589: A4~410.958904109589 + C5~410.958904109589 + G5^410.958904109589,
410.958904109589,
410.958904109589: C4~410.958904109589 + E5^410.958904109589,
410.958904109589: F4~410.958904109589 + F5^410.958904109589,
410.958904109589: G4~410.958904109589,
410.958904109589: A4~410.958904109589 + C5~410.958904109589 + E5^410.958904109589,
410.958904109589,
410.958904109589: D4~410.958904109589 + G5^410.958904109589,
410.958904109589: F4~410.958904109589,
410.958904109589: G4~410.958904109589 + F5^410.958904109589,
410.958904109589: A4~410.958904109589 + D5~410.958904109589,
410.958904109589: E5^410.958904109589,
410.958904109589: D4~410.958904109589,
410.958904109589: F4~410.958904109589 + D5^410.958904109589,
410.958904109589: G4~410.958904109589,
410.958904109589: A4~410.958904109589,
410.958904109589: C5^410.958904109589 + C4~410.958904109589,
410.958904109589: D5^410.958904109589 + F4~410.958904109589 + D4~410.958904109589,
410.958904109589`;
const frogTune = tune`
300: C5-1000 + C4^300,
200: E4/200,
300: G4-300 + C4^300,
200: E4/200,
300: C#5-1000 + C#4^300,
200: F4/200,
300: G#4-300 + C#4^300,
200: F4/200,
300: D5-1000 + D4^300,
200: F#4/200,
300: A4-300 + D4^300,
200: F#4/200,
300: C#5-1000 + C#4^300,
200: F4/200,
300: G#4-300 + C#4^300 + E5~300,
200: F4/200 + D5~200,
0`; // X
const snakeTune = tune`
122.95081967213115: C4~122.95081967213115 + C5^122.95081967213115 + G4/122.95081967213115,
122.95081967213115: E4~122.95081967213115 + E5^122.95081967213115 + B4/122.95081967213115 + G4-122.95081967213115,
122.95081967213115: G4~122.95081967213115 + G5^122.95081967213115 + C4~122.95081967213115 + E5/122.95081967213115,
122.95081967213115: B4~122.95081967213115 + B5^122.95081967213115 + E4^122.95081967213115 + G5/122.95081967213115,
122.95081967213115: C5~122.95081967213115 + G4^122.95081967213115 + B5/122.95081967213115,
122.95081967213115: E5~122.95081967213115 + E4-122.95081967213115 + B4^122.95081967213115,
122.95081967213115: G5~122.95081967213115 + G4-122.95081967213115 + C5^122.95081967213115 + C4~122.95081967213115,
122.95081967213115: B5~122.95081967213115 + B4-122.95081967213115 + E5^122.95081967213115 + E4/122.95081967213115,
122.95081967213115: C5-122.95081967213115 + G5^122.95081967213115 + G4/122.95081967213115,
122.95081967213115: C4~122.95081967213115 + E5-122.95081967213115 + B4-122.95081967213115 + B5^122.95081967213115,
122.95081967213115: E4~122.95081967213115 + G5-122.95081967213115 + C5-122.95081967213115,
122.95081967213115: G4~122.95081967213115 + E5-122.95081967213115,
122.95081967213115: B4~122.95081967213115 + G5-122.95081967213115 + E4/122.95081967213115 + E5/122.95081967213115,
122.95081967213115: C5~122.95081967213115 + B5-122.95081967213115 + G4/122.95081967213115 + G5/122.95081967213115 + C4~122.95081967213115,
122.95081967213115: E5~122.95081967213115 + E4-122.95081967213115 + B5/122.95081967213115,
122.95081967213115: G5~122.95081967213115 + G4-122.95081967213115,
122.95081967213115: B5~122.95081967213115 + B4-122.95081967213115 + E5/122.95081967213115,
122.95081967213115: C5-122.95081967213115 + C4~122.95081967213115 + G5/122.95081967213115,
122.95081967213115: B4-122.95081967213115 + E5-122.95081967213115 + E4^122.95081967213115 + B5/122.95081967213115,
122.95081967213115: C5-122.95081967213115 + G5-122.95081967213115 + G4^122.95081967213115 + C4~122.95081967213115,
122.95081967213115: E4~122.95081967213115 + E5-122.95081967213115 + B4^122.95081967213115,
122.95081967213115: G4~122.95081967213115 + G5-122.95081967213115 + C5^122.95081967213115,
122.95081967213115: B4~122.95081967213115 + B5-122.95081967213115 + E5^122.95081967213115,
122.95081967213115: C5~122.95081967213115 + G5^122.95081967213115 + E4/122.95081967213115,
122.95081967213115: E5~122.95081967213115 + C4~122.95081967213115 + B5^122.95081967213115 + G4/122.95081967213115,
122.95081967213115: G5~122.95081967213115 + E4~122.95081967213115 + B4/122.95081967213115,
122.95081967213115: B5~122.95081967213115 + G4~122.95081967213115 + C5/122.95081967213115,
122.95081967213115: B4~122.95081967213115 + E5/122.95081967213115,
122.95081967213115: C5~122.95081967213115 + C4~122.95081967213115 + G5/122.95081967213115 + E4/122.95081967213115,
122.95081967213115: E5~122.95081967213115 + E4^122.95081967213115 + B5/122.95081967213115 + G4/122.95081967213115,
122.95081967213115: G5~122.95081967213115 + G4^122.95081967213115 + E5/122.95081967213115 + C4~122.95081967213115,
122.95081967213115: B5~122.95081967213115 + B4^122.95081967213115 + G5/122.95081967213115 + E4-122.95081967213115`;
const catTune = tune`
461.53846153846155: E5^461.53846153846155 + G4^461.53846153846155 + C5^461.53846153846155 + E4~461.53846153846155 + C4~461.53846153846155,
461.53846153846155: E4~461.53846153846155 + F5^461.53846153846155,
461.53846153846155: C5~461.53846153846155 + G5^461.53846153846155,
461.53846153846155: G4~461.53846153846155,
461.53846153846155: C4^461.53846153846155 + E5^461.53846153846155,
461.53846153846155: E4~461.53846153846155,
461.53846153846155: A4~461.53846153846155 + A5^461.53846153846155,
461.53846153846155: G4~461.53846153846155,
461.53846153846155: D4^461.53846153846155 + F5^461.53846153846155,
461.53846153846155: G4~461.53846153846155,
461.53846153846155: B4~461.53846153846155 + G5^461.53846153846155,
461.53846153846155: F4~461.53846153846155 + F5^461.53846153846155,
461.53846153846155: E4^461.53846153846155 + E5^461.53846153846155,
461.53846153846155: B4~461.53846153846155,
461.53846153846155: G4~461.53846153846155,
461.53846153846155: D4~461.53846153846155 + B4-461.53846153846155,
461.53846153846155: C4^461.53846153846155 + C5-461.53846153846155,
461.53846153846155: E4~461.53846153846155 + G5/461.53846153846155,
461.53846153846155: C5~461.53846153846155 + E5/461.53846153846155,
461.53846153846155: G4~461.53846153846155 + B4-461.53846153846155,
461.53846153846155: C4^461.53846153846155 + C5-461.53846153846155,
461.53846153846155: E4~461.53846153846155 + A4/461.53846153846155,
461.53846153846155: A4~461.53846153846155 + C5/461.53846153846155,
461.53846153846155: G4~461.53846153846155 + A5/461.53846153846155,
461.53846153846155: D4^461.53846153846155 + G5/461.53846153846155,
461.53846153846155: F4~461.53846153846155,
461.53846153846155: G4~461.53846153846155 + F5/461.53846153846155,
461.53846153846155: E4~461.53846153846155 + E5-461.53846153846155,
461.53846153846155: E4~461.53846153846155 + G4^461.53846153846155 + C5-461.53846153846155,
461.53846153846155: B4~461.53846153846155,
461.53846153846155: C5~461.53846153846155,
461.53846153846155: F5^461.53846153846155 + B4^461.53846153846155 + D4~461.53846153846155 + F4~461.53846153846155 + G4^461.53846153846155`; // S. M.
const ratTune = tune`
128.75536480686696: D5^128.75536480686696 + C4~128.75536480686696 + A5-128.75536480686696,
128.75536480686696: A4^128.75536480686696,
128.75536480686696: D5^128.75536480686696,
128.75536480686696: C5^128.75536480686696 + C4~128.75536480686696,
128.75536480686696: B4^128.75536480686696,
128.75536480686696: A4^128.75536480686696,
128.75536480686696: D5^128.75536480686696 + C4~128.75536480686696,
128.75536480686696: A4^128.75536480686696,
128.75536480686696: D5^128.75536480686696,
128.75536480686696: C4~128.75536480686696 + G5^128.75536480686696,
128.75536480686696: A5^128.75536480686696,
128.75536480686696: C4~128.75536480686696 + F5^128.75536480686696,
128.75536480686696: C4~128.75536480686696 + D5^128.75536480686696,
128.75536480686696: A4^128.75536480686696,
128.75536480686696: C5^128.75536480686696,
128.75536480686696: C4~128.75536480686696 + B4^128.75536480686696,
128.75536480686696: A4^128.75536480686696,
128.75536480686696: G4^128.75536480686696,
128.75536480686696: C4~128.75536480686696 + A4^128.75536480686696,
128.75536480686696: F4^128.75536480686696,
128.75536480686696: G4^128.75536480686696,
128.75536480686696: C4~128.75536480686696 + F4^128.75536480686696,
128.75536480686696: E4^128.75536480686696,
128.75536480686696: D4^128.75536480686696,
0`; // X
const whitetailTune = tune`
142.85714285714286: F5/142.85714285714286 + D4~142.85714285714286 + F4-142.85714285714286,
142.85714285714286: D5~142.85714285714286,
142.85714285714286: D4~142.85714285714286 + F5/142.85714285714286,
142.85714285714286: D5/142.85714285714286 + D4-142.85714285714286,
142.85714285714286: D4~142.85714285714286,
142.85714285714286: E5/142.85714285714286 + D5~142.85714285714286 + E4-142.85714285714286,
142.85714285714286: D4~142.85714285714286,
142.85714285714286: D5~142.85714285714286 + E5/142.85714285714286,
142.85714285714286: F5/142.85714285714286 + D4~142.85714285714286 + F4-142.85714285714286,
142.85714285714286: D5~142.85714285714286,
142.85714285714286: D4~142.85714285714286 + F5/142.85714285714286,
142.85714285714286: A5/142.85714285714286 + D5~142.85714285714286 + A4-142.85714285714286,
142.85714285714286: D4~142.85714285714286,
142.85714285714286: G5/142.85714285714286 + D5~142.85714285714286 + G4-142.85714285714286,
142.85714285714286: D4~142.85714285714286,
142.85714285714286: F5/142.85714285714286 + D5~142.85714285714286 + F4-142.85714285714286,
142.85714285714286: F4~142.85714285714286,
142.85714285714286: F5~142.85714285714286,
142.85714285714286: D5/142.85714285714286 + F4~142.85714285714286 + D4-142.85714285714286,
142.85714285714286: F5~142.85714285714286 + D4^142.85714285714286,
142.85714285714286: F4~142.85714285714286 + D5^142.85714285714286,
142.85714285714286: F5~142.85714285714286 + D4^142.85714285714286,
142.85714285714286: F4~142.85714285714286 + D5^142.85714285714286,
142.85714285714286: F5~142.85714285714286 + D4^142.85714285714286,
142.85714285714286: C4~142.85714285714286 + D5^142.85714285714286,
142.85714285714286: C5~142.85714285714286 + A5^142.85714285714286 + A4-142.85714285714286,
142.85714285714286: C4~142.85714285714286,
142.85714285714286: C5~142.85714285714286 + G5^142.85714285714286 + G4-142.85714285714286,
142.85714285714286: C4~142.85714285714286,
142.85714285714286: C5~142.85714285714286 + F5^142.85714285714286 + F4-142.85714285714286,
142.85714285714286: C4~142.85714285714286,
142.85714285714286: C5~142.85714285714286 + E5^142.85714285714286 + E4-142.85714285714286`;

const unlockTune = tune`
40.65040650406504: G5-40.65040650406504 + F5-40.65040650406504 + E5-40.65040650406504 + A5-40.65040650406504,
40.65040650406504: A5-40.65040650406504 + F5-40.65040650406504 + E5-40.65040650406504,
40.65040650406504,
40.65040650406504: F5-40.65040650406504 + E5-40.65040650406504 + G5-40.65040650406504 + D5-40.65040650406504,
40.65040650406504: D5-40.65040650406504 + E5-40.65040650406504,
1097.560975609756`;
const solveTune = tune`
0: A4~1,
100: A4/100,
100: G#4/100,
100: F4/100,
100: B3/100,
100: A#3/100,
100: F#4/100,
100: A#4/100,
100: D5/100,
1`; // X
const chestTune = tune`
74.07407407407408: C5-74.07407407407408,
74.07407407407408: D5-74.07407407407408,
74.07407407407408: E5-74.07407407407408,
74.07407407407408: D5-74.07407407407408 + G5-74.07407407407408,
74.07407407407408: E5-74.07407407407408 + A5-74.07407407407408,
74.07407407407408: F5-74.07407407407408,
74.07407407407408: G5-74.07407407407408 + E5-74.07407407407408,
74.07407407407408: A5-74.07407407407408 + F5-74.07407407407408,
1777.7777777777778`;
const summonTune = tune``;
const noToolTune = tune`
61.47540983606557: D5^61.47540983606557 + E5^61.47540983606557,
61.47540983606557: D5^61.47540983606557 + B4^61.47540983606557,
1844.2622950819673`;
const resetTune = tune`
280.3738317757009: C5~280.3738317757009,
280.3738317757009: E5~280.3738317757009 + B4~280.3738317757009,
8411.214953271028`;
const fireWandTune = tune`
37.92667509481669: E5/37.92667509481669 + D5/37.92667509481669 + C5/37.92667509481669 + F5/37.92667509481669 + G5/37.92667509481669,
37.92667509481669: E5/37.92667509481669 + A5/37.92667509481669,
37.92667509481669: D5/37.92667509481669 + C5/37.92667509481669 + E5/37.92667509481669 + G5/37.92667509481669,
37.92667509481669: B4/37.92667509481669 + F5/37.92667509481669,
37.92667509481669: B4/37.92667509481669 + A4/37.92667509481669 + C5/37.92667509481669 + E5/37.92667509481669,
37.92667509481669: A4/37.92667509481669 + D5/37.92667509481669,
37.92667509481669: G4/37.92667509481669 + C5/37.92667509481669,
37.92667509481669: F4/37.92667509481669,
37.92667509481669: E4/37.92667509481669,
872.3135271807838`;
const speedBootsTune = tune`
54.6448087431694: E5^54.6448087431694,
54.6448087431694: F5^54.6448087431694,
54.6448087431694: G5^54.6448087431694 + D5^54.6448087431694,
54.6448087431694: A5^54.6448087431694 + E5^54.6448087431694,
1530.0546448087432`;
const harmonicaTunes = [
  tune`
500: C4/500,
15500`,
  tune`
500: D4/500,
15500`,
  tune`
500: E4/500,
15500`,
  tune`
500: F4/500,
15500`,
  tune`
500: G4/500,
15500`,
  tune`
106.38297872340425: A4/106.38297872340425,
3297.8723404255315`,
  tune`
500: B4/500,
15500`,
  tune`
500: C5/500,
15500`
];

const textSettings = {x: 0, y: 0, color: color`2`};

const dirVectorTable = {
  "w": [0, -1],
  "s": [0, 1],
  "a": [-1, 0],
  "d": [1, 0]
};

/* 
   6  7   0
   5 null 1
   4  3   2
*/
const vectorIndexTable = {
  "0": {
    "0": null,
    "1": 3,
    "-1": 7
  },
  "1": {
    "0": 1,
    "1": 2,
    "-1": 0
  },
  "-1": {
    "0": 5,
    "1": 4,
    "-1": 6
  }
};

const empty = ".";

const bloomedFlowersSprite = bitmap`
................
..HHH...........
.HH8HH..........
.H868H.....HHH..
.HH8HH....HH8HH.
..HHH..HHHH868H.
......HH8HHH8HH.
......H868HHHH..
...HHHHH8HH.....
..HH8HHHHH......
..H868H..HHH....
..HH8HH.HH8HH...
...HHH..H868H...
........HH8HH...
.........HHH....
................`;
const boxButtonSprite = bitmap`
................
................
................
................
...0000000000...
...0CCCCCCCC0...
...0CC0CC0CC0...
...0CCC00CCC0...
...0CCC00CCC0...
...0CC0CC0CC0...
..L0CCCCCCCC0L..
..LLLLLLLLLLLL..
..LLLLLLLLLLLL..
..LLLLLLLLLLLL..
................
................`;
const boxDoorSprite = bitmap`
0111111111111110
01LLLLLLLLLLLLL0
01LLLLLLLLLLLLL0
01LL000000001LL0
01LL00CCCC001LL0
01LL0C0CC0C01LL0
01LL0C0000C01LL0
01LL0CC00CC01LL0
01LL0CC00CC01LL0
01LL0C0000C01LL0
01LL0C0CC0C01LL0
01LL00CCCC001LL0
01LL000000001LL0
01LL111111111LL0
01LLLLLLLLLLLLL0
0000000000000000`;
const normalLegend = [
  ["harmonicaIndicator", bitmap`
................
................
................
................
....66666666....
....66....66....
....6.6..6.6....
....6..66..6....
....6..66..6....
....6.6..6.6....
....66....66....
....66666666....
................
................
................
................`],
  ["fire", bitmap`
................
................
........3.......
.....3.33.......
....393393......
....3939993.....
....39399933....
...399399993....
...3999399933...
...3399399993...
...3339399993...
...3993939933...
...3339393393...
....33933333....
.....3399333....
......3333......`],
  ["fireTrail", bitmap`
................
................
................
................
.......3...33...
.......333.3....
........393.....
.....3.39933....
....3939339.3...
.....333993.....
....3999333.....
.....33333......
................
................
................
................`],
  ["fireWand", bitmap`
................
.6666666........
.6333936........
.6393336........
.6339396........
.6939996........
.6999996........
.6666666........
....6...........
....6...........
....6...........
....6...........
....6...........
....6...........
....6...........
................`],
  ["speedBoots", bitmap`
................
................
................
................
................
................
................
................
................
................
................
................
..555...5555....
55575...5775555.
57775...5777775.
55555...5555555.`],
  ["harmonica", bitmap`
................
................
................
................
................
................
................
................
..66666666666666
.66060606060606.
..66666666666666
................
................
................
................
................`],
  ["jail", bitmap`
................
..LLLLLLLLLLLL..
.LLL..L..L..LLL.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.L.L..L..L..L.L.
.LLLLLLLLLLLLLL.`],
  ["duckSinger", bitmap`
....66666.......
...66666666.....
..66666666666...
..66006666666...
.9960066666666..
99966666666666..
.....666666666..
.....6F6666666..
.....6F6666666..
....666FF66F66..
....66666FF666..
...66666666666..
9.99666669666...
.9966...696.....
99......999.....
.9.....9.9.9....`],
  ["catSinger", bitmap`
......9..9......
.....99..99.....
.....999999.....
...2.409049.2...
....24090492....
...2.999999.2...
.......999......
..99...9999.....
..9.....999.....
..99...9999.....
...99.99999.....
....9999999.....
....9999999.....
...999999999....
..999999999999..
................`],
  ["snakeSinger", bitmap`
................
................
................
................
....8..DDDDD....
.....88D606DDDD.
....8..DDDDDDDD.
.............DD.
....DDDDDDDDDDD.
....DDDDDDDDDDD.
....DD..........
....DDDDDDDDDDD.
....DDDDDDDDDDD.
.............DD.
...DDDDDDDDDDDD.
..DDDDDDDDDDDDD.`],
  ["frogSinger", bitmap`
................
......DDDD......
.....DDDDDDD....
....DDDDDDDDD...
...DD606DDDDDD..
...DD606DDDDDDD.
...DD606DDDDDDD.
....DDDDDDDDDDD.
.....DDDDDDDDDDD
......DDDDDDDDDD
........DDDDDDDD
..99.DDDDDDDDDDD
...99DDDDDDDDD..
..999DDDDDD.....
...9............
................`],
  ["ratSinger", bitmap`
...222...222....
...282...282....
...282...282....
...222222222....
...222222222....
...223323322....
...223223222.8..
...222222222..8.
....2228222..88.
....2222222..8..
....2222222...8.
....22222228888.
....2222222.....
....2222222.....
....2222222.....
................`],
  ["whitetailSinger", bitmap`
................
................
................
................
.CC....CC.......
.22C...2C.......
..CCCCCCCCCCCCC2
..C20C20C2CC2C2.
..C00C00CC2CC2C.
..CCCCCCCCCC2CC.
....CCC..C.CCCCC
....CCC..C.C.C.C
....008..C.C.C.C
.....88.CC.C.C.C
........0..0.0.0
................`],
  ["box", bitmap`
................
.00000000000000.
.00CCCCCCCCCC00.
.0C0CCCCCCCC0C0.
.0CC0CCCCCC0CC0.
.0CCC0CCCC0CCC0.
.0CCCC0CC0CCCC0.
.0CCCCC00CCCCC0.
.0CCCCC00CCCCC0.
.0CCCC0CC0CCCC0.
.0CCC0CCCC0CCC0.
.0CC0CCCCCC0CC0.
.0C0CCCCCCCC0C0.
.00CCCCCCCCCC00.
.00000000000000.
................`],
  ["player", bitmap`
................
................
...999999999....
...9999999999...
.9999999999999..
990009999000999.
990009999000999.
990009999000999.
999999999999999.
...9......99999.
...9......9.....
...9.....99.....
...9.....99.....
...9.....99.....
...9.....99.....
.999.....99999..`],
  ["boss", bitmap`
................
...3.......33...
...333222333....
...2233232222...
.2222222222222..
223332222333222.
223332222333222.
223332222333222.
222222222222222.
...2......22222.
...2......2.....
...2.....22.....
...2.....22.....
...2.....22.....
...2.....22.....
.222.....22222..`],
  ["wall", bitmap`
LLL0LLL0LLL0LLL0
LLL0LLL0LLL0LLL0
LLL0LLL0LLL0LLL0
0000000000000000
L0LLL0LLL0LLL0LL
L0LLL0LLL0LLL0LL
L0LLL0LLL0LLL0LL
0000000000000000
0LLL0LLL0LLL0LLL
0LLL0LLL0LLL0LLL
0LLL0LLL0LLL0LLL
0000000000000000
LL0LLL0LLL0LLL0L
LL0LLL0LLL0LLL0L
LL0LLL0LLL0LLL0L
0000000000000000`],
  ["wallSecret", bitmap`
LLL0LLL0LLL0LLL0
L0L0LLL0L0L0LL00
L000LLL00LL00L00
0000000000000000
L0LLL0L0L0L0L0LL
L0L0L0L0L00L000L
L000L0LL00LLL0L0
0000000000000000
0L0L0L0L0LLL0LLL
000L0LL00LL000LL
0LLL0L0000LL0L00
0000000000000000
LL0LL000L000LL0L
LL0L0L0L0L0LL00L
LL0LLL0LLL0LLL0L
0000000000000000`],
  ["wallMusic", bitmap`
LLL0LLL0LLL0LLL0
LLL0LLL0L220LLL0
LLL0LLL0L220LLL0
0000000002200000
L0LLL0LLL22LL0LL
L0LLL0LLL22LL0LL
L0LLL0LLL22LL0LL
0000000002200000
0LL22222222L0LLL
0L222222222L0LLL
0L222222222L0LLL
0022222222200000
LL222222220LLL0L
LL0L2222LL0LLL0L
LL0LLL0LLL0LLL0L
0000000000000000`],
  ["wallBoss", bitmap`
8888888888888888
H8HHH88H88H88888
H8888HH888HH8H88
HH8H88HH8H8888H8
HHHHH8HH8HHH88H8
H8888H8HHH8HH888
HHHH8888H888H888
H888H8H8H8HH8HH8
HHH8HHH888HHH888
HH888HHH88HH8HH8
H88HH888H8888HH8
H8888HH8H8HH8888
H8HHHHHHHHHHH8H8
HHHH8H88H8H8H8H8
H888HH88H8H8H888
HHHHHHHHHHHHHHHH`],
  ["voidDecoration", bitmap`
...L...L...L...L
...L...L...L...L
...L...L...L...L
LLLLLLLLLLLLLLLL
.L...L...L...L..
.L...L...L...L..
.L...L...L...L..
LLLLLLLLLLLLLLLL
L...L...L...L...
L...L...L...L...
L...L...L...L...
LLLLLLLLLLLLLLLL
..L...L...L...L.
..L...L...L...L.
..L...L...L...L.
LLLLLLLLLLLLLLLL`],
  ["wallIce", bitmap`
7770777077707770
7770777077707770
7770777077707770
0000000000000000
7770777077707770
7770777077707770
7770777077707770
0000000000000000
7770777077707770
7770777077707770
7770777077707770
0000000000000000
7770777077707770
7770777077707770
7770777077707770
0000000000000000`],
  ["wallIceRemains", bitmap`
77...77..77.7.7.
7..............7
...............7
................
7...............
...............7
...............7
................
...............7
7...............
................
...............7
7...............
................
7...............
.77..7.77....7..`],
  ["floor", bitmap`
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000
0000000000000000`],
  ["floor2", bitmap`
................
................
................
...H............
................
............H...
................
........H.......
................
................
....H...........
................
..........H.....
................
................
................`],
  ["greyPillar", bitmap`
................
.LLLLLLLLLLLLLL.
.L1L1L1L1L1L11L.
.LLL1L1L1L1L1LL.
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
.LLL1L1L1L1L1LL.
.L1L1L1L1L1L11L.
.LLLLLLLLLLLLLL.`],
  ["pedestal", bitmap`
................
.....H33977.....
.....HH4466.....
.....443876.....
.....498465.....
.....988547.....
.....995575.....
................
.LLLLLLLLLLLLLL.
.L1L1L1L1L1L11L.
.LLL1L1L1L1L1LL.
..L1L1L1L1L1L1..
..L1L1L1L1L1L1..
.LLL1L1L1L1L1LL.
.L1L1L1L1L1L11L.
.LLLLLLLLLLLLLL.`],
  ["chest", bitmap`
................
................
................
..000000000000..
.00222222222200.
.02222222222220.
.02222222222220.
.00000000000000.
..022020020220..
..022022220220..
..022022220220..
..022022220220..
..022022220220..
..022022220220..
..000000000000..
................`],
  ["hole", bitmap`
................
................
..LLLLLLLLLLLL..
..LLLLLLLLLLLL..
..LL11111111LL..
..LL12222221LL..
..LL12222221LL..
..LL12222221LL..
..LL12222221LL..
..LL12222221LL..
..LL12222221LL..
..LL11111111LL..
..LLLLLLLLLLLL..
..LLLLLLLLLLLL..
................
................`],
  ["musicNotes", bitmap`
............2...
..2.........22..
..2.........2...
.22..2222...2...
.22..2..2..22...
.....2..2..22...
....22.22.......
....22.22.......
................
...2............
...2.....2......
...2.....22.....
..22.....2......
..22.....2......
........22......
........22......`],
  ["sign", bitmap`
................
.CCCCCCCCCCCCCC.
.C00000000C000C.
.CCCCCCCCCCCCCC.
.C000C00C00000C.
.CCCCCCCCCCCCCC.
.C00C00C0C0000C.
.CCCCCCCCCCCCCC.
.......CC.......
.......CC.......
.......CC.......
.......CC.......
.......CC.......
................
................
................`],
  ["button", bitmap`
................
................
................
................
................
................
....00000000....
....0HHHHHH0....
....0HHHHHH0....
....0HHHHHH0....
...L0HHHHHH0L...
...L00000000L...
...LLLLLLLLLL...
................
................
................`],
  ["door", bitmap`
0111111111111110
01LLLLLLLLLLLLL0
01LLLLLLLLLLLLL0
01LL000000001LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL0HHHHHH01LL0
01LL000000001LL0
01LL111111111LL0
01LLLLLLLLLLLLL0
0000000000000000`]
];

function generateDoorAndButtonSet(colorName, color, legend) {
  const replaceColor = `H`;
  var buttonWhite;
  var doorWhite;
  for (var i = 0; i < legend.length; i++) {
    if (legend[i][0] === "button") buttonWhite = legend[i][1];
    if (legend[i][0] === "door") doorWhite = legend[i][1];
  }
  legend.push(["button" + colorName, buttonWhite.replaceAll(replaceColor, color)]);
  legend.push(["door" + colorName, doorWhite.replaceAll(replaceColor, color)]);
}

generateDoorAndButtonSet("Red", color`3`, normalLegend);
generateDoorAndButtonSet("Blue", color`5`, normalLegend);
generateDoorAndButtonSet("Green", color`4`, normalLegend);

var flashLegend = [];

function grayscaleBitmap(bitmap) {
  var greyscaledBitmap = "";
  for (var i = 0; i < bitmap.length; i++) {
    if (bitmap[i] === "\n") {
      greyscaledBitmap += "\n";
      continue;
    }

    switch (bitmap[i]) {
      case color`.`:
        greyscaledBitmap += color`.`;
        break;
      case color`0`:
      case color`L`:
        greyscaledBitmap += color`2`;
        break;
      case color`C`:
        greyscaledBitmap += color`3`;
        break;
      case color`F`:
        greyscaledBitmap += color`6`;
        break;
      case color`D`:
        greyscaledBitmap += color`4`;
        break;
      case color`H`:
        greyscaledBitmap += color`8`;
        break;
      case color`3`:
        greyscaledBitmap += color`C`;
        break;
      case color`6`:
        greyscaledBitmap += color`F`;
        break;
      case color`4`:
        greyscaledBitmap += color`D`;
        break;
      case color`8`:
        greyscaledBitmap += color`H`;
        break;
      
      default:
        greyscaledBitmap += color`0`;
        break;
    }
    
  }

  return greyscaledBitmap;
}

// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
function cyrb53(str, seed = 0) {
    let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
    for(let i = 0, ch; i < str.length; i++) {
        ch = str.charCodeAt(i);
        h1 = Math.imul(h1 ^ ch, 2654435761);
        h2 = Math.imul(h2 ^ ch, 1597334677);
    }
    h1  = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
    h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
    h2  = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
    h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
  
    return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}

function setLegendAndSprites(sprites) {
  // Create global variables with sprites
  for (var i = 0; i < sprites.length; i++) {
    const spriteName = sprites[i][0];
    const char = String.fromCharCode(cyrb53(spriteName) % 285);
    eval(spriteName + " = \"" + char + "\";");
    sprites[i][0] = char;
  }
  
  setLegend.apply(this, sprites);
}

function swapLegend(sprites) {
  setLegend.apply(this, sprites);
}

setLegendAndSprites(normalLegend);
setBoxPuzzleComponents();

for (var i = 0; i < normalLegend.length; i++) {
  flashLegend.push([normalLegend[i][0], grayscaleBitmap(normalLegend[i][1])]);
}

function flash(duration) {
  swapLegend(flashLegend);
  setTimeout(function () {
    swapLegend(normalLegend);
  }, duration);
}

function setBoxPuzzleComponents() {
  for (var i = 0; i < normalLegend.length; i++) {
    if (normalLegend[i][0] == button) {
      normalLegend[i][1] = boxButtonSprite;
      continue;
    }

    if (normalLegend[i][0] == door) {
      normalLegend[i][1] = boxDoorSprite;
      continue;
    }
  }

  swapLegend(normalLegend);
}

function bloomFlowers() {
  for (var i = 0; i < normalLegend.length; i++) {
    if (normalLegend[i][0] == floor2) {
      normalLegend[i][1] = bloomedFlowersSprite;
      swapLegend(normalLegend);
      return;
    }
  }
}

const singers = [duckSinger, frogSinger, snakeSinger, catSinger, ratSinger, whitetailSinger];
const decoration = [floor, floor2, sign, greyPillar];
const speedBootsSkippable = [boss, hole, musicNotes, button, buttonRed, buttonBlue, buttonGreen, ...decoration, ...singers];
const solids = [player, greyPillar, wall, wallIce, wallMusic, wallBoss, box, jail, hole, door, doorRed, doorBlue, doorGreen, ...singers];
const pushables = [box, jail, ...singers];
setSolids(solids);
setBackground(floor);
setPushables({
  [player]: [...pushables]
});

var room = {x: 2, y: 0};
var roomEnter = {x: 1, y: 1};
const startRoom = {x: -2, y: 0};

// 6x7 map size
const rooms = [
  [map`

ÿ.¤....¤.ÿ
.....(...
.*......e.
¤........¤
.û..Ñx..¸.
..........
ÿ.¤....¤.ÿ
....`, map`
°°°°°°°°°°°°
°°°°°°°°°°°°
°°
°°ÿ¤.ÿ.ÿ...
°°¤..3.3...
°°...ÿ.ÿ...
°°ÿ3ÿ
°°...°°°°°
°°ÿ3ÿ°°°°°`, map`

......¤
.ÿ¤......
.....
........Ñ»..
.....¤...
i....ß
¤.X...¤
.`, map`
°°°°°
..°°°°°
...
..¤.......
......¤.....
....¤..
°°°¤....
°°°....
°°°.`, map`

..........
.ñ.¤......3
.......¤..3
...........
Â
°°°°°°°..ñ
°°°°°°°¤..
°°°°°°°...`, map`

..........
..e¶e¶.¤.X.
...........
.e¶e¶.¤.¤.
..........
.e¶e¶.¤.¤.
..........
.`],
  [map`
°°°¤...°°°
°°°ÿ..°°°
°°°...¤°°°
°°°.ÿ°°°
°°°¤...°°°
°°°.°°°
°°°...¤°°°
°°°ÿ..ÿ°°°
°°°¤...°°°`, map`
°°ÿ.ÿ°°°°°
°°..¤°°°°°
°°ÿgÿ°°°°°
°°¤..°°°°°
°°ÿgÿ°°°°°
°°..¤°°°°°
°°ÿgÿ°°°°°
°°¤..°°°°°
°°ÿ.ÿ°°°°°`, map`
.
ČČ.Č....Č.
ČČ¤.....¤.
Č....¶*...
.........Č
..X......Č
Č....¤....
ČČ.ČČ......
`, map`
.
¤.....¤Ñ(
..
.¤.».....
..
¤...XX÷.¤

.¤.....¤....
`, map`
°°°°...
°°°°.¤.¤
°°°°.....
°°°°.¤.¤.¤
.....
¤X¤¤.¤.¤
.........
.¤ñ¤Â.¤.¤.¤
...`, map`
°°°°°°°.
°°°°°°°¤.X
g
.ÿ.ÿ°Ñ
.¤.X.°
.....ÿ°°°°
..¤.¤.°°°°
...ÿ°°°°
.°°°°`],
  [map`
°°°....°°°
°ÿ..ÿ°°°
°3.¤...°°°
°gÿGGÿ°°°
°.....¤°°°
°ÿ..ÿ°°°
°°°¤..X°°°
°°°ÿ..ÿ°°°
°°°....°°°`, map`
°°...°°°°°
°°ÿ.ÿ°°°°°
°°*.¤°°°°°
°°ÿ.ÿ°°°°°
°°...°°°°°
°°÷÷÷°°°°°
°°...°°°°°
°°ÿ¤ÿ°°°°°
°°...°°°°°`, map`

ñ.......¤
...
¤....¤¤.
3....
X.g¤.¤.¤¤.
33.
.Â........»
.`, map`
°°
°°.¤.....ß
.....¤.
.33....ñ..
.ÂĖ....
...y¤...
......ß
..¤.......
¤ii`, map`
...°°
ñ....¤.°°
.......°°
33..3....°°
.Â..Ė.¤..
.....ÿ
°°°°.y.¤..
°°°°.....ÿ
°°°°...`, map`
.
.....Ė.¤ÿ
...gg¤X¤
.....ÿ¤ÿ
.GG
°3...°°°°
°g÷÷°°
°.....û°°
°.y°°`],
  [map`
°°°¤..¤°°°
°°°ÿ..ÿ°°°
°°°¤..¤
°°°ÿ...ÿ¤ÿ.
°°°........
°°°ÿ...ÿ¤ÿ.
°°°..
°°°.°°°
°°°»°°°`, map`
°°...°°°°°
°°ÿ¤ÿ°°°°°
...°°°°°
.ÿ..¤.°°°°°
.¤.¤ÿ¤°°°°°
.ÿ..¤.°°°°°
°°°°°
°°°°°°°°°°°°
°°°°°°°°°°°°`, map`
.
.....¤.¤.
3ÿÿÿ..
.¤.¤.¤..
3ÿ.ÿÿ.
¤.¤.....Â
....Ė
yñ....3.
.`, map`
...
......XX
..X.¤.
¤....¸¶....
.¶..¸.¤.
°°°.¸..¶...
°°°..¶¸..
°°°ÿ.....°
°°°°`, map`
°°°°°.¤.°°
°°°°°...°°
333
.....3...3..
..¤..3.¤.3.¤
.....3...3..
333
°°°°°...°°
°°°°°.¤.°°`, map`
÷÷
..........
.ÿ.¤..¤.ÿ.
..........
..¤......X.
....Ñ..¶...
.ÿ.¤..¤.ÿ.
..........
`],
  [map`
.
X.G...¤.
.¤..G..X
»
°°°°°°°°°°
»
Xÿ..¤ÿ..¤ÿ.
..¤ÿ..¤ÿ..¤
`, map`
°°°°°°°°°°°°
°°°°°°°°°°°°
°°°°°°°°°°°°
°°°°°
°°°°°¸..¤.
.ÿ
.ÿ.¤.÷.ÿ.¤°
.¤.ÿ.÷.¤.ÿ°
°`, map`
.
.ÿ¤ÿ.ÿ..
.......ÿ
X.........
.....¤.Ñ....
¤..........
........ÿ
..ÿ.ÿ..»
`, map`
°°°
°°...3...°
...3...
......3.....
..¤.¤.3.¤.¤.
......3.....
...3..
.X...3..°°
°°`, map`
...
333.......
3y33..¤.33»
.33333....3
.¤3.3ñ3.¤..
.33333.....
3633..g
333..¤ÂĖĉ.
.`, map`
°°°°°°°
¤.X°°
....Â..ñ.°°
..¤.°°
¶¶.
°°°...ûû.¤
°°°.......
°°°¤......
°°°.`],
  [map`

.g..yñ.¤
g6......
.g...g.
.g.G.g..
g.g..g¤.
¤...¤..
X¤.¤.¤.ÂĖĉ.
`, map`

ñg¤.gg..
g.g.¤..
.g....X
.....¤.
.ĖÂ.y¤.ĉ
..
......¤..6
.`, map`

.g.ÿXÿÑÿ.ÿ..
.g.....¤....
GG
..¤......¤..
GG
...¤....¤..
.....
¤..°°°`, map`

.3¤.¤Xy¤
.3..Ė....
.....
ñy6.ĉ.......
..÷..
.3..Â...¤
.3¤.¤ñXe
`, map`
.
..Â.3.ñ...
.g
..¤...¤...¤.
............
¤...¤...¤...
gĖ
...y..X...
`, map`
°°°»
°°°ÿ..¤..ÿ
.......
..3...¤...
..3g¤.¤Ñ¤.¤
..3...¤...
.......
°°°ÿ..¤..ÿ
°°°÷÷÷`],
  [map`

¤...¤....¤.
Â
.ÿ.ÿÑÿ.ÿ.ÿ
...3...3..
...3...3..
...3...÷..
¤ÿ.ÿ.ÿ.÷ñ.
`, map`
.°
.¤...¤...¤°
»°
...°
.....°

..3.3......
÷..3..33....
`, map`
...
..¤..¤....
..
....¤....¤
......Ñ..
...X.....
÷........
÷....¤..
`, map`

..ÿ.ÿ.ÿ.ÿ.ÿ
.¤.¤.¤.¤.¤
........(.
.¤.¤.¤.¤.¤
ÿ¤ÿ.ÿ¤ÿ
°°°¤X¤.¤¶¤
°°°ÿ¤ÿ.ÿ¤ÿ
°°°`, map`

ÿ..ÿ..ÿ..ÿ
..........
ÿ..¤..¤....
....x......
ÿ..¤..¤....
..........
ÿ..ÿ..ÿ..ÿ
`, map`
...
¤G¤.¤.¤.¤G
.GGG.G.GGG
.¤GÑG.G..¤.
.GGGGGGG.GG
.¤.GX..G.¤G
G.GGG...GG
¤.¤.¤.¤.¤.
`]
];

function generateSecretCode() {
  number = "";
  for (var i = 0; i < 4; i++) {
    number += Math.floor(Math.random() * 7) + 1;
  }
  return parseInt(number);
}

const totalCollectibles = "27";
var collectibles = 0;
var codesFound = [];
var found = {
  fireWand: false,
  speedBoots: false,
  harmonica: false,
  duck: false,
  frog: false,
  snake: false,
  cat: false,
  rat: false,
  whitetail: false
};
var duckCode = generateSecretCode();
var frogCode = generateSecretCode();
var snakeCode = generateSecretCode();
var catCode = generateSecretCode();
var ratCode = generateSecretCode();
var whitetailCode = generateSecretCode();

var toolEquipped = null;
var harmonicaBuffer = [];
var harmonicaNotesPlayed = [];
var fireActive = false;
var fireClearInterval = null;
var bossActive = false;
var bossSpawnTimeout = null;
var bossMoveInterval = null;

var song = playTune(startTune, Infinity);
var songState = 0; // 0: Start, -1: Singer, -2: Boss, 1: Main, 2: Icy

function setTilePermanently(x, y, tile) {
  const index = 1 + x + (y * (width() + 1));

  rooms[room.y][room.x] = getRoom().substring(0, index) + tile + getRoom().substring(index + 1);
}

function getRoom() {
  return rooms[room.y][room.x];
}

setMap(getRoom());
addSprite(roomEnter.x, roomEnter.y, player);

// Handle boss
function moveBoss() {
  const p = getFirst(player);
  const b = getFirst(boss);

  if (b.x > p.x) b.x--;
  if (b.x < p.x) b.x++;
  if (b.y > p.y) b.y--;
  if (b.y < p.y) b.y++;

  if (p.x === b.x && p.y === b.y) {
    room.x = 4;
    room.y = 6;
    setMap(getRoom());
    addSprite(6, 4, player);
    roomChanged();
    toolEquipped = null;
    killBoss();
    flash(150);

    song.end();
    song = playTune(mainTune, Infinity);
    songState = 1;
  }
}
function summonBoss(x, y) {
  if (bossMoveInterval) clearInterval(bossMoveInterval);
  bossActive = true;
  bossSpawnTimeout = setTimeout(function () {
    addSprite(x, y, boss);
    bossMoveInterval = setInterval(moveBoss, 1000);
    bossSpawnTimeout = null;

    song.end();
    song = playTune(bossTune, Infinity);
    songState = -2;
  }, 2000)
}
function killBoss() {
  if (getFirst(boss)) getFirst(boss).remove();
  if (bossMoveInterval) clearInterval(bossMoveInterval);
  if (bossSpawnTimeout) clearTimeout(bossSpawnTimeout);
  bossMoveInterval = null;
  bossSpawnTimeout = null;
  bossActive = false;

  song.end();
  song = playTune(mainTune, Infinity);
  songState = 1;
}
function clearBossWalls() {
  const bossWalls = getAll(wallBoss);
  for (var i = 0; i < bossWalls.length; i++) {
    bossWalls[i].remove();
  }
}

function handleMusic() {
  // Override everything for boss
  if (bossActive && songState !== -2) {
    song.end();
    song = playTune(bossTune, Infinity);
    songState = -2;
  }

  // Switch from start theme
  if (songState === 0 && startRoom.x + room.x >= 2) {
    song.end();
    song = playTune(mainTune, Infinity);
    songState = 1;
  }
  
  // Switch from start to main<->icy
  if (songState === 2 && startRoom.y + room.y < 5) {
    song.end();
    song = playTune(mainTune, Infinity);
    songState = 1;
  } else if (songState === 1 && startRoom.y + room.y >= 5) {
    song.end();
    song = playTune(icyTune, Infinity);
    songState = 2;
  }

  // Handle secret songs
  switch ((startRoom.x + room.x) + "," + (startRoom.y + room.y)) {
    case "0,1":
      found.duck = true;
      song.end();
      song = playTune(duckTune, Infinity);
      songState = -1;
      break;
    case "1,3":
      found.frog = true;
      song.end();
      song = playTune(frogTune, Infinity);
      songState = -1;
      break;
    case "3,0":
      found.snake = true;
      song.end();
      song = playTune(snakeTune, Infinity);
      songState = -1;
      break;
    case "3,3":
      found.cat = true;
      song.end();
      song = playTune(catTune, Infinity);
      songState = -1;
      break;
    case "3,4":
      found.rat = true;
      song.end();
      song = playTune(ratTune, Infinity);
      songState = -1;
      break;
    case "1,6":
      found.whitetail = true;
      song.end();
      song = playTune(whitetailTune, Infinity);
      songState = -1;
      break;
    default:
      if (songState === -1) {
        song.end();
        song = playTune(startRoom.y + room.y >= 5 ? icyTune : mainTune, Infinity);
        songState = startRoom.y + room.y >= 5 ? 2 : 1;
      }
      break;
  }
}

function roomChanged() {
  if (bossSpawnTimeout) clearTimeout(bossSpawnTimeout);
  clearInterval(fireClearInterval);
  fireActive = false;
  harmonicaNotesPlayed = [];

  if (bossActive) {
    summonBoss(roomEnter.x, roomEnter.y);
    clearBossWalls();
  }
  
  handleMusic();
}

function checkDirNextRoom(dir) {
  const p = getFirst(player);
  switch (dir) {
    case "w":
      if (p.y <= 0) {
        room.y--;
        
        setMap(getRoom());
        roomEnter.x = p.x;
        roomEnter.y = height() - 1;
        addSprite(roomEnter.x, roomEnter.y, player);

        roomChanged();
        return true;
      }
      break;
    case "s":
      if (p.y >= height() - 1) {
        room.y++;
        
        setMap(getRoom());
        roomEnter.x = p.x;
        roomEnter.y = 0;
        addSprite(roomEnter.x, roomEnter.y, player);

        roomChanged();
        return true;
      }
      break;
    case "a":
      if (p.x <= 0) {
        room.x--;
        
        setMap(getRoom());
        roomEnter.x = width() - 1;
        roomEnter.y = p.y;
        addSprite(roomEnter.x, roomEnter.y, player);

        roomChanged();
        return true;
      }
      break;
    case "d":
      if (p.x >= width() - 1) {
        room.x++;
        
        setMap(getRoom());
        roomEnter.x = 0;
        roomEnter.y = p.y;
        addSprite(roomEnter.x, roomEnter.y, player);

        roomChanged();
        return true;
      }
      break;
  }
}

onInput("w", () => {
  if (toolEquipped) {
    useTool("w");
    return;
  }
  if (checkDirNextRoom("w")) return;
  getFirst(player).y -= 1;
});

onInput("s", () => {
  if (toolEquipped) {
    useTool("s");
    return;
  }
  if (checkDirNextRoom("s")) return;
  getFirst(player).y += 1;
});

onInput("a", () => {
  if (toolEquipped) {
    useTool("a");
    return;
  }
  if (checkDirNextRoom("a")) return;
  getFirst(player).x -= 1;
});

onInput("d", () => {
  if (toolEquipped) {
    useTool("d");
    return;
  }
  if (checkDirNextRoom("d")) return;
  getFirst(player).x += 1;
});

// Tool buttons

onInput("k", () => {
  setMap(getRoom());
  toolEquipped = null;

  if (fireClearInterval) clearInterval(fireClearInterval);
  fireActive = false;
  harmonicaBuffer = [];
  
  addSprite(roomEnter.x, roomEnter.y, player);
  
  if (bossActive) {
    killBoss();
    summonBoss(roomEnter.x, roomEnter.y);
  }

  playTune(resetTune);
});

// Firewand
onInput("j", () => {
  if (!found.fireWand) {
    playTune(noToolTune);
    return;
  }

  if (toolEquipped === fireWand) {
    toolEquipped = null;
    getFirst(fireWand).remove();
    return;
  } else if (toolEquipped !== fireWand) {
    if (toolEquipped) {
      if (getFirst(speedBoots)) getFirst(speedBoots).remove();
      if (getFirst(harmonica)) getFirst(harmonica).remove();
      if (getFirst(harmonicaIndicator)) getFirst(harmonicaIndicator).remove();
    }
    const p = getFirst(player);
    toolEquipped = fireWand;
    addSprite(p.x, p.y, fireWand);
    return;
  }
});

// Harmonica
onInput("i", () => {
  if (!found.harmonica) {
    playTune(noToolTune);
    return;
  }

  if (toolEquipped === harmonica) {
    toolEquipped = null;
    harmonicaBuffer = [];
    harmonicaNotesPlayed = [];
    getFirst(harmonica).remove();
    if (getFirst(harmonicaIndicator)) getFirst(harmonicaIndicator).remove();
    return;
  } else if (toolEquipped !== harmonica) {
    if (toolEquipped) {
      if (getFirst(speedBoots)) getFirst(speedBoots).remove();
      if (getFirst(fireWand)) getFirst(fireWand).remove();
    }
    const p = getFirst(player);
    toolEquipped = harmonica;
    addSprite(p.x, p.y, harmonica);
    return;
  }
});

// Speed Boots
onInput("l", () => {
  if (!found.speedBoots) {
    playTune(noToolTune);
    return;
  }

  if (toolEquipped === speedBoots) {
    toolEquipped = null;
    getFirst(speedBoots).remove();
    return;
  } else if (toolEquipped !== speedBoots) {
    if (toolEquipped) {
      if (getFirst(fireWand)) getFirst(fireWand).remove();
      if (getFirst(harmonica)) getFirst(harmonica).remove();
      if (getFirst(harmonicaIndicator)) getFirst(harmonicaIndicator).remove();
    }
    const p = getFirst(player);
    toolEquipped = speedBoots;
    addSprite(p.x, p.y, speedBoots);
    return;
  }
});

function useTool(dir) {
  switch (toolEquipped) {
    case fireWand:
      useFireWand(dir);
      break;
    case speedBoots:
      useSpeedBoots(dir);
      break;
    case harmonica:
      useHarmonica(dir);
      break;
  }
}

function useFireWand(dir) {
  if (fireActive) return;
  fireActive = true;
  playTune(fireWandTune);
  
  const dx = dirVectorTable[dir][0];
  const dy = dirVectorTable[dir][1];

  const p = getFirst(player);
  
  var x = p.x;
  var y = p.y;

  var trailLength = 1;

  var tile = getTile(x, y)[0];

  while (true) {
    if (x + dx < 0 || y + dy < 0 || x + dx >= width() || y + dy >= height()) break;
    
    x += dx;
    y += dy;

    // Get topmost tile
    tile = getTile(x, y);
    tile = tile[tile.length - 1];

    if (tile && solids.includes(tile.type)) {
      if (tile.type === wallIce) {
        tile.remove();
        addSprite(x, y, wallIceRemains);
        setTilePermanently(x, y, wallIceRemains);
      }
      break;
    }

    addSprite(x, y, fireTrail);
    trailLength++;
  }

  addSprite(x, y, fire);

  var cleared = 1;
  fireClearInterval = setInterval(function () {
    if (trailLength === cleared) {
      clearInterval(fireClearInterval);
      getFirst(fire).remove();
      fireActive = false;
      return;
    }

    const fireTrails = getAll(fireTrail);
    fireTrails[Math.floor(Math.random() * fireTrails.length)].remove();
    cleared++;
  }, 700 / trailLength);
}

function useSpeedBoots(dir) {
  playTune(speedBootsTune);
  const p = getFirst(player);
  const dx = dirVectorTable[dir][0];
  const dy = dirVectorTable[dir][1];

  const tile = getTile(p.x + dx, p.y + dy)[0];
  if (!tile || speedBootsSkippable.includes(tile.type)) {
    p.x += dx * 2;
    p.y += dy * 2;
    
    const b = getFirst(speedBoots);
    b.x = p.x;
    b.y = p.y;
  }
}

function useHarmonica(dir) {
  
  if (harmonicaBuffer.push(dir) < 2) return;
  if (harmonicaNotesPlayed.length >= 4) harmonicaNotesPlayed = [];
  if (getFirst(harmonicaIndicator)) getFirst(harmonicaIndicator).remove();

  var dx = dirVectorTable[harmonicaBuffer[0]][0];
  var dy = dirVectorTable[harmonicaBuffer[0]][1];
  if (harmonicaBuffer[1] && (harmonicaBuffer[0] !== harmonicaBuffer[1])) {
    dx += dirVectorTable[harmonicaBuffer[1]][0];
    dy += dirVectorTable[harmonicaBuffer[1]][1];
  }
  
  const tuneIndex = vectorIndexTable[dx][dy];

  if (tuneIndex === null) {
    harmonicaBuffer = [];
    return;
  }
  
  const p = getFirst(player);
  addSprite(p.x + dx, p.y + dy, harmonicaIndicator);
  playTune(harmonicaTunes[tuneIndex]);
  harmonicaBuffer = [];

  if (harmonicaNotesPlayed.push(tuneIndex) >= 4) {
    if (checkHarmonicaCode()) clearMusicWalls();
  }
}

function readSecretCode() {
  switch ((startRoom.x + room.x) + "," + (startRoom.y + room.y)) {
    case "0,1":
      return duckCode;
    case "1,3":
      return frogCode;
    case "3,0":
      return snakeCode;
    case "3,3":
      return catCode;
    case "3,4":
      return ratCode;
    case "1,6":
      return whitetailCode;
    default:
      return -1;
  }
}

function readSign() {
  switch ((startRoom.x + room.x) + "," + (startRoom.y + room.y)) {
    case "0,0":
      return "Walls cracks";
    case "1,1":
      return "Please find my\nsibling and play\nhis song to me.";
    case "3,1":
      return "Play " + fluteTeleport;
    case "3,3":
      return "To the notes\n\nS. M.";
    case "3,5":
      return "Play " + fluteExit;
    case "-2,6":
      return "Play " + fluteIceTest;
    case "0,4":
      return "Press 'j' to use\nthe fireWand.";
    case "0,6":
      return "Press 'i' to use\nthe harmonica\nPlay " + fluteTest;
    case "0,5":
      return "Press 'l' to use\nthe speedBoots"
    case "-2,0":
      return "You win!";
    case "3,6":
      return "He makes purple\ngo away.";
    default:
      return "Invalid Sign";
  }
}

function openChest() {
  switch ((startRoom.x + room.x) + "," + (startRoom.y + room.y)) {
    case "0,4":
      found.fireWand = true;
      addText("Found the\nfireWand!", textSettings);
      break;
    case "0,5":
      found.speedBoots = true;
      addText("Found the\nspeedBoots!", textSettings);
      break;
    case "0,6":
      found.harmonica = true;
      addText("Found the\nharmonica!", textSettings);
      break;
    default:
      collectibles++;
      addText("Flowers: " + collectibles, textSettings);
      if (collectibles === parseInt(totalCollectibles)) setTimeout(function() {bloomFlowers();}, 800);
      break;
  }
}

function clearMusicWalls() {
  flash(150);
  playTune(solveTune);
  
  const musicWalls = getAll(wallMusic);
  for (var i = 0; i < musicWalls.length; i++) {
    setTilePermanently(musicWalls[i].x, musicWalls[i].y, empty);
    musicWalls[i].remove();
  }
}

function openDoor(door) {
  const d = getFirst(door);
  if (d) {
    clearTile(d.x, d.y);
    playTune(unlockTune);
  }
}

function checkTileUnderPlayer(tile) {
  const p = getFirst(player);
  switch (tile.type) {
    case pedestal:
      if (bossActive) {
        killBoss();
        flash(150);
      } else {
        summonBoss(0, 4);
        flash(2000);
        playTune(summonTune);
      }
      break;
    case buttonRed:
      openDoor(doorRed);
      break;
    case buttonBlue:
      openDoor(doorBlue);
      break;
    case buttonGreen:
      openDoor(doorGreen);
      break;
    case chest:
      setTilePermanently(p.x, p.y, empty);
      tile.remove();
      openChest();
      playTune(chestTune);
      break;
    case sign:
      addText(readSign(), textSettings);
      break;
  }
}

function checkSingerSecretCompleted() {
  const totalMusicNotes = getAll(musicNotes).length;
  if (totalMusicNotes === 0) return false;
  
  for (var i = 0; i < singers.length; i++) {
    if (tilesWith(musicNotes, singers[i]).length === totalMusicNotes) return true;
  }
  
  return false;
}

function displayHarmonicaUI() {
  var harmonicaNotesDisplay = "";
  var stillEntering = 0;
  for (var i = 0; i < 4; i++) {
    if (harmonicaNotesPlayed[i] === undefined) {
      if (i === stillEntering) {
        if (harmonicaBuffer.length > 0) {
          harmonicaNotesDisplay += "@";
        } else {
          harmonicaNotesDisplay += "*";
        }
        continue;
      }
      harmonicaNotesDisplay += "_";
      continue;
    }
    harmonicaNotesDisplay += harmonicaNotesPlayed[i].toString();
    stillEntering++;
  }
  addText(harmonicaNotesDisplay, {x: 16, y: 14, color: color`2`});

  // Display text numbers around player
  const p = getFirst(player);
  const coeff = 1.44;
  addText("6 7 0\n\n5   1\n\n4 3 2", {x: Math.floor(p.x * coeff), y: Math.floor(p.y * coeff), color: color`6`});
}

function checkHarmonicaCode() {
  code = parseInt(harmonicaNotesPlayed.join(""));
  
  if (code == fluteTeleport) {
    
    flash(150);
    room.x = 5;
    room.y = 1;
    setMap(getRoom());
    roomEnter.x = 5;
    roomEnter.y = 5;
    addSprite(roomEnter.x, roomEnter.y, player);
    roomChanged();
    toolEquipped = null;
    
    return false;
  }
  
  switch ((startRoom.x + room.x) + "," + (startRoom.y + room.y)) {
    case "-1,2":
      return code == duckCode;
    case "-1,4":
      return code == frogCode;
    case "0,6":
      return code == fluteTest;
    case "1,1":
      return code == whitetailCode;
    case "-1,6":
      return code == fluteBoxes;
    case "3,5":
      return code == fluteExit;
    case "3,2":
      return code == ratCode;
    case "1,5":
      return code == snakeCode;
    case "3,3":
      return code == catCode;
    case "-2,6":
      return code == fluteIceTest;
    default:
      return false;
  }
}

afterInput(() => {
  clearText();

  const p = getFirst(player);
  
  const tile1 = getTile(p.x, p.y)[1];
  const tile2 = getTile(p.x, p.y)[2];
  const tile = tile1 ? (tile2 ? tile2 : tile1) : null;
  
  if (tile) checkTileUnderPlayer(tile);
  
  if (checkSingerSecretCompleted()) {
    if (!codesFound.includes(readSecretCode())) {
      flash(150);
      playTune(solveTune);
      codesFound.push(readSecretCode());
    }
    addText(readSecretCode().toString(), {x: 0, y: 14, color: color`2`});
  }


  // Check for box puzzle components
  const bbs = getAll(button);
  for (let i = 0; i < bbs.length; i++) {
    const btile = getTile(bbs[i].x, bbs[i].y)[0];
    if (btile && (btile.type == box || btile.type == jail)) {
      openDoor(door);
      bbs[i].remove();
    }
  }

  if (toolEquipped === harmonica) displayHarmonicaUI();
  if ((startRoom.x + room.x) + "," + (startRoom.y + room.y) == "-2,0") {
    addText("Flowers: " + collectibles + "/" + totalCollectibles, {x: 0, y: 14, color: color`2`});
    if (!found.duck) addSprite(2, 3, jail);
    if (!found.rat) addSprite(2, 5, jail);
    if (!found.cat) addSprite(4, 2, jail);
    if (!found.whitetail) addSprite(7, 2, jail);
    if (!found.snake) addSprite(9, 3, jail);
    if (!found.frog) addSprite(9, 5, jail);
  }
});
